

#ifdef __unix__

#include "DirMonitorForLinux.h"

QStringList getAllChildDirPathList(const QString &path)
{
    QStringList filePathList;
    QFileInfo info(path);
    if (info.exists()) {
        if (info.isDir()) {
            const QList<QFileInfo> &infoList = QDir(path).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks);
            for (const QFileInfo &i : infoList) {
                filePathList.append(i.absoluteFilePath());
                // 递归扫描目录内容
                const QStringList &filePathListTmp = getAllChildDirPathList(i.absoluteFilePath());
                filePathList.append(filePathListTmp);
            }
        }
    } else {
        qCritical() << Q_FUNC_INFO << path << "not exists!";
    }

    return filePathList;
}

DirMonitorForLinux::DirMonitorForLinux(QObject *parent)
    : DirMonitor(parent)
{
    m_fd = inotify_init1(IN_NONBLOCK);
    if(m_fd < 0)
    {
        qCritical() << Q_FUNC_INFO << "inotify_init failed, Error no." << errno << strerror(errno);
    }

    QTimer *timer = new QTimer(this);
    timer->setInterval(300);

    // connect
    connect(timer, &QTimer::timeout, this, &DirMonitorForLinux::monitorFileEvent);

    // post init
    timer->start();
}

DirMonitorForLinux::~DirMonitorForLinux()
{
    close(m_fd);
}

void DirMonitorForLinux::SetRootDirPath(const QString &path)
{
    addDirPath(path);
    const QList<QString> &dirPathList = getAllChildDirPathList(path);
    for (const QString &pathTmp : dirPathList) {
        addDirPath(pathTmp);
    }
}

void DirMonitorForLinux::addDirPath(const QString &path)
{
    if (!QFileInfo(path).isDir()) {
        return;
    }

    uint32_t mask = IN_CLOSE_WRITE | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE | IN_DELETE;
    int watchDesc = inotify_add_watch(m_fd, path.toUtf8().data(), mask);
    if(watchDesc < 0)
    {
        qCritical() << Q_FUNC_INFO << "inotify_add_watch failed" << path;
        qCritical() << Q_FUNC_INFO << "Error no." << errno << strerror(errno);
        return;
    }

    m_mapOfWatchDescToDirPath.insert(watchDesc, path);
}

void DirMonitorForLinux::rmDirPath(const QString &path)
{
    QMap<int, QString>::ConstIterator cIt = m_mapOfWatchDescToDirPath.constBegin();
    for (; cIt != m_mapOfWatchDescToDirPath.constEnd(); cIt++) {
        if (path == cIt.value()) {
            break;
        }
    }
    if (cIt == m_mapOfWatchDescToDirPath.constEnd()) {
        qDebug() << Q_FUNC_INFO << "not find path in watch list" << path;
        return;
    }

    int watchDesc = cIt.key();
    watchDesc = inotify_rm_watch(m_fd, watchDesc);
    if(watchDesc < 0)
    {
        qCritical() << Q_FUNC_INFO << "inotify_rm_watch failed" << path;
        qCritical() << Q_FUNC_INFO << "Error no." << errno << strerror(errno);
        return;
    }

    m_mapOfWatchDescToDirPath.remove(watchDesc);
}

void DirMonitorForLinux::clearDirPaths()
{
    QMap<int, QString>::ConstIterator cIt = m_mapOfWatchDescToDirPath.constBegin();
    for (; cIt != m_mapOfWatchDescToDirPath.constEnd(); cIt++) {
        int watchDesc = cIt.key();
        watchDesc = inotify_rm_watch(m_fd, watchDesc);
        if(watchDesc < 0)
        {
            qCritical() << Q_FUNC_INFO << "inotify_rm_watch failed" << cIt.value();
            qCritical() << Q_FUNC_INFO << "Error no." << errno << strerror(errno);
            continue;
        }
    }

    m_mapOfWatchDescToDirPath.clear();
}

void DirMonitorForLinux::onFileClosed(int watchDesc, char *name)
{
    const QString &dirPath = m_mapOfWatchDescToDirPath.value(watchDesc);
    // qDebug() << Q_FUNC_INFO << dirPath << name;

    const QString &path = QDir::cleanPath(dirPath + QDir::separator() + name);
    Q_EMIT FileClosed(path);
}

void DirMonitorForLinux::onFileMovedOut(int watchDesc, char *name)
{
    const QString &dirPath = m_mapOfWatchDescToDirPath.value(watchDesc);
    qDebug() << Q_FUNC_INFO << dirPath << name;

    const QString &path = QDir::cleanPath(dirPath + QDir::separator() + name);
    if (m_mapOfWatchDescToDirPath.values().contains(path)) {
        rmDirPath(path);
    }
    Q_EMIT FileMovedOut(path);
}

void DirMonitorForLinux::onFileMovedIn(int watchDesc, char *name)
{
    const QString &dirPath = m_mapOfWatchDescToDirPath.value(watchDesc);
    qDebug() << Q_FUNC_INFO << dirPath << name;

    const QString &path = QDir::cleanPath(dirPath + QDir::separator() + name);
    if (QFileInfo(path).isDir()) {
        addDirPath(path);
    }
    Q_EMIT FileMovedIn(path);
}

void DirMonitorForLinux::onFileCreated(int watchDesc, char *name)
{
    const QString &dirPath = m_mapOfWatchDescToDirPath.value(watchDesc);
    qDebug() << Q_FUNC_INFO << dirPath << name;

    const QString &path = QDir::cleanPath(dirPath + QDir::separator() + name);
    if (QFileInfo(path).isDir()) {
        addDirPath(path);
    }
    Q_EMIT FileCreated(path);
}

void DirMonitorForLinux::onFileDeleted(int watchDesc, char *name)
{
    const QString &dirPath = m_mapOfWatchDescToDirPath.value(watchDesc);
    qDebug() << Q_FUNC_INFO << dirPath << name;

    const QString &path = QDir::cleanPath(dirPath + QDir::separator() + name);
    if (m_mapOfWatchDescToDirPath.values().contains(path)) {
        rmDirPath(path);
    }

    Q_EMIT FileDeleted(path);
}

void DirMonitorForLinux::monitorFileEvent()
{
    struct inotify_event *event;
    int length;
    int nread;
    char buf[BUFSIZ];
    buf[sizeof(buf) - 1] = 0;

    // 监控
    length = read(m_fd, buf, sizeof(buf) - 1);
    nread = 0;
    if (length == -1 and errno == EAGAIN) {
        return;
    }

    // inotify 事件发生时
    while(length > 0)
    {
        event = (struct inotify_event *)&buf[nread];
        // 判断事件是否发生
        if( (event->mask & IN_CLOSE_WRITE))
        {
            onFileClosed(event->wd, event->name);
        }
        if( (event->mask & IN_MOVED_FROM))
        {
            onFileMovedOut(event->wd, event->name);
        }
        if( (event->mask & IN_MOVED_TO))
        {
            onFileMovedIn(event->wd, event->name);
        }
        if( (event->mask & IN_CREATE))
        {
            onFileCreated(event->wd, event->name);
        }
        if( (event->mask & IN_DELETE))
        {
            onFileDeleted(event->wd, event->name);
        }

        nread = nread + sizeof(struct inotify_event) + event->len;
        length = length - sizeof(struct inotify_event) - event->len;
    }
}

#endif
